/*
 * Copyright (c) 2012-2017 Red Hat, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Red Hat, Inc. - initial API and implementation
 */
package org.eclipse.che.ide.websocket.rest;

import com.google.gwt.http.client.Response;
import java.util.List;
import org.eclipse.che.ide.commons.exception.UnmarshallerException;
import org.eclipse.che.ide.rest.AsyncRequestLoader;
import org.eclipse.che.ide.rest.HTTPHeader;
import org.eclipse.che.ide.rest.HTTPStatus;
import org.eclipse.che.ide.rest.RequestStatusHandler;
import org.eclipse.che.ide.websocket.Message;
import org.eclipse.che.ide.websocket.MessageBuilder;
import org.eclipse.che.ide.websocket.rest.exceptions.ServerException;
import org.eclipse.che.ide.websocket.rest.exceptions.UnauthorizedException;

/**
 * Callback to receive a {@link Message}.
 *
 * @author Artem Zatsarynnyi
 */
public abstract class RequestCallback<T> {

  // http code 207 is "Multi-Status"
  // IE misinterpreting HTTP status code 204 as 1223
  // (http://www.mail-archive.com/jquery-en@googlegroups.com/msg13093.html)
  private static final int[] DEFAULT_SUCCESS_CODES = {
    Response.SC_OK, Response.SC_CREATED, Response.SC_NO_CONTENT, 207, 1223
  };
  /** Deserializer for the body of the {@link Message}. */
  private final Unmarshallable<T> unmarshaller;
  /** Status codes of the successful responses. */
  private int[] successCodes;
  /** An object deserialized from the response. */
  private T payload;
  /** Handler to show an execution state of operation. */
  private RequestStatusHandler statusHandler;
  /** Loader to show while request is calling. */
  private AsyncRequestLoader loader;

  public RequestCallback() {
    this.successCodes = DEFAULT_SUCCESS_CODES;
    this.unmarshaller = null;
  }

  /**
   * Constructor retrieves unmarshaller with initialized (this is important!) object. When response
   * comes then callback calls {@link Unmarshallable#unmarshal(com.codenvy.ide.websocket.Message)}
   * which populates the object.
   *
   * @param unmarshaller {@link Unmarshallable}
   */
  public RequestCallback(Unmarshallable<T> unmarshaller) {
    this.successCodes = DEFAULT_SUCCESS_CODES;
    this.unmarshaller = unmarshaller;
  }

  /**
   * Perform actions when response message was received.
   *
   * @param message message
   */
  public void onReply(Message message) {
    if (loader != null) {
      loader.hide();
    }

    final String uuid = message.getStringField(MessageBuilder.UUID_FIELD);
    if (message.getResponseCode() == HTTPStatus.UNAUTHORIZED) {
      UnauthorizedException exception = new UnauthorizedException(message);
      if (statusHandler != null) {
        statusHandler.requestError(uuid, exception);
      }
      onFailure(exception);
      return;
    }

    if (isSuccessful(message)) {
      try {
        if (unmarshaller != null) {
          unmarshaller.unmarshal(message);
          payload = unmarshaller.getPayload();
        }
        if (statusHandler != null) {
          statusHandler.requestFinished(uuid);
        }
        onSuccess(payload);
      } catch (UnmarshallerException e) {
        if (statusHandler != null) {
          statusHandler.requestError(uuid, e);
        }
        onFailure(e);
      }
    } else {
      ServerException exception = new ServerException(message);
      if (statusHandler != null) {
        statusHandler.requestError(uuid, exception);
      }
      onFailure(exception);
    }
  }

  /**
   * Is response successful?
   *
   * @param response {@link Message}
   * @return <code>true</code> if response is successful and <code>false</code> if response is not
   *     successful
   */
  protected final boolean isSuccessful(Message response) {
    if (successCodes == null) {
      successCodes = DEFAULT_SUCCESS_CODES;
    }

    List<Pair> headers = response.getHeaders().toList();
    if (headers != null) {
      for (Pair header : headers) {
        if (HTTPHeader.JAXRS_BODY_PROVIDED.equals(header.getName())
            && "Authentication-required".equals(header.getValue())) {
          return false;
        }
      }
    }

    for (int code : successCodes) if (response.getResponseCode() == code) return true;

    return false;
  }

  /**
   * Set the array of successful HTTP status codes.
   *
   * @param successCodes the successCodes to set
   */
  public void setSuccessCodes(int[] successCodes) {
    this.successCodes = successCodes;
  }

  /** Get handler to show an execution state of request. */
  public final RequestStatusHandler getStatusHandler() {
    return statusHandler;
  }

  /**
   * Set handler to show an execution state of request.
   *
   * @param handler status handler
   */
  public final void setStatusHandler(RequestStatusHandler handler) {
    this.statusHandler = handler;
  }

  /** Get the loader to show while request is calling. */
  public final AsyncRequestLoader getLoader() {
    return loader;
  }

  /**
   * Set the loader to show while request is calling.
   *
   * @param loader loader to show while request is calling
   */
  public final void setLoader(AsyncRequestLoader loader) {
    this.loader = loader;
  }

  /**
   * Invokes if response is successfully received and response status code is in set of success
   * codes.
   *
   * @param result
   */
  protected abstract void onSuccess(T result);

  /**
   * Invokes if an error received from the server.
   *
   * @param exception caused failure
   */
  protected abstract void onFailure(Throwable exception);
}
